package org.appfuse.mojo.exporter;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.appfuse.mojo.HibernateExporterMojo;
import org.appfuse.tool.AppFuseExporter;
import org.appfuse.tool.ArtifactInstaller;
import org.codehaus.plexus.components.interactivity.Prompter;
import org.codehaus.plexus.components.interactivity.PrompterException;
import org.hibernate.tool.hbm2x.Exporter;
import org.hibernate.tool.hbm2x.GenericExporter;
import org.hibernate.tool.hbm2x.pojo.EntityPOJOClass;
import org.hibernate.tool.hbm2x.pojo.POJOClass;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Generates Java classes from set of annotated POJOs. Use -DdisableInstallation
 * to prevent installation. If using this goal in a "core" module or project,
 * only DAOs and Managers will be created. For "web" modules, the same principle
 * applies.
 * 
 * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a>
 * @goal gen
 * @phase generate-sources
 * @execute phase="compile"
 */
public class AppFuseGeneratorMojo extends HibernateExporterMojo {
	String functionType;
	String masterPojoName=null,detailPojoName=null;
	
	boolean generateCoreOnly;
	boolean generateWebOnly;
	String pojoName;

	/**
	 * This is a prompter that can be user within the maven framework.
	 * 
	 * @component
	 */
	Prompter prompter;

	/**
	 * The path where the generated artifacts will be placed. This is
	 * intentionally not set to the default location for maven generated
	 * sources. This is to keep these files out of the eclipse/idea generated
	 * sources directory as the intention is that these files will be copied to
	 * a source directory to be edited and modified and not re generated each
	 * time the plugin is run. If you want to regenerate the files each time you
	 * build the project just set this value to
	 * ${basedir}/target/generated-sources or set the flag on eclipse/idea
	 * plugin to include this file in your project file as a source directory.
	 * 
	 * @parameter expression="${appfuse.destinationDirectory}"
	 *            default-value="${basedir}"
	 * @noinspection UnusedDeclaration
	 */
	private String destinationDirectory;

	/**
	 * The directory containing the source code.
	 * 
	 * @parameter expression="${appfuse.sourceDirectory}"
	 *            default-value="${basedir}/target/appfuse/generated-sources"
	 * @noinspection UnusedDeclaration
	 */
	private String sourceDirectory;

	/**
	 * Allows disabling installation - for tests and end users that don't want
	 * to do a full installation
	 * 
	 * @parameter expression="${appfuse.disableInstallation}"
	 *            default-value="false"
	 */
	private boolean disableInstallation;

	/**
	 * @parameter expression="${appfuse.genericCore}" default-value="true"
	 * @noinspection UnusedDeclaration
	 */
	private boolean genericCore;

	/**
	 * @parameter expression="${appfuse.templateDirectory}"
	 *            default-value="${basedir}/src/test/resources"
	 * @noinspection UnusedDeclaration
	 */
	private String templateDirectory;

	/**
	 * Path for where to generate files at.
	 */

	private String fullPath = null;

	/**
	 * Default constructor.
	 */
	public AppFuseGeneratorMojo() {
		addDefaultComponent("target/appfuse/generated-sources",
				"configuration", false);
		addDefaultComponent("target/appfuse/generated-sources",
				"annotationconfiguration", true);
	}

	// --------------------- Interface ExporterMojo ---------------------

	/**
	 * Returns <b>gen</b>.
	 * 
	 * @return String goal's name
	 */
	public String getName() {
		return "gen";
	}

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {
		// if project is of type "pom", throw an error
		if (getProject().getPackaging().equalsIgnoreCase("pom")) {
			String errorMsg = "[ERROR] This plugin cannot be run from a pom project, please run it from a jar or war project (i.e. core or web).";
			// getLog().error(errorMsg);
			throw new MojoFailureException(errorMsg);
		}
		/*
		 * TODO 此处加入命令行参数输入
		 */
		functionType = System.getProperty("ftype");
		if (functionType == null) {
			try {
				functionType = prompter
						.prompt("please select your function type ,single entity:1 master detail:2?",
								"1");
			} catch (PrompterException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if (functionType.equalsIgnoreCase("1")) {
			if (functionType == null || "".equals(functionType.trim())) {
				throw new MojoExecutionException(
						"You must specify an entity name to continue.");
			}

			pojoName = System.getProperty("entity");

			if (pojoName == null) {
				try {
					pojoName = prompter
							.prompt("What is the name of your pojo (i.e. Person)?");
				} catch (PrompterException pe) {
					pe.printStackTrace();
				}
			}
			genExec();
		} else {
			
			try {
				masterPojoName = prompter
						.prompt("please input master pojo Name with full package path,and the entity java file in model package,like org.sbbs.app.model.DemoEntity:");
				detailPojoName = prompter
						.prompt("please input detail pojo Name with full package path,and the entity java file in model package,like org.sbbs.app.model.DemoEntity:");

			} catch (PrompterException e) {
				e.printStackTrace();
			}
			pojoName = masterPojoName;
			this.processPojoName();
			addEntityToHibernateCfgXml();
			pojoName = detailPojoName;
			this.processPojoName();
			addEntityToHibernateCfgXml();
			
			
			
			pojoName = masterPojoName;
			this.processPojoName();
			super.execute();
			pojoName = detailPojoName;
			this.processPojoName();
			super.execute();
			
		}

	
	}

	
	private void genExec()throws MojoExecutionException, MojoFailureException{
		this.processPojoName();
		addEntityToHibernateCfgXml();
		super.execute();
	}
	private void processPojoName() throws MojoExecutionException, MojoFailureException{
		if (pojoName == null || "".equals(pojoName.trim())) {
			throw new MojoExecutionException(
					"You must specify an entity name to continue.");
		}

		// A dot in the entity name means the person is specifying the
		// package.
		if (pojoName.contains(".")) {
			if (pojoName.indexOf("model") == -1) {
				throw new MojoExecutionException(
						"You must specify 'model' as the last package in your entity name.");
			}
			System.setProperty("entity", pojoName);
			fullPath = pojoName.substring(0, pojoName.indexOf(".model"));
			// allow ~ to be used for groupId
			fullPath = fullPath.replace("~", getProject().getGroupId());

			pojoName = pojoName.substring(pojoName.lastIndexOf(".") + 1);
			log("Package name set to: " + fullPath);
		}
	}
	/**
	 * TODO 好像额外的信息需要在这里进行添加配置，以供具体的导出使用
	 * @see org.appfuse.mojo.HibernateExporterMojo#configureExporter(org.hibernate.tool.hbm2x.Exporter)
	 */
	protected Exporter configureExporter(Exporter exp)
			throws MojoExecutionException {
		// Read in AppFuseExporter#configureExporter to decide if a class should
		// be generated or not
		System.setProperty("appfuse.entity", pojoName);

		// add output directory to compile roots
		getProject().addCompileSourceRoot(
				new File(getComponent().getOutputDirectory()).getPath());

		// now set the extra properties for the AppFuseExporter
		AppFuseExporter exporter = (AppFuseExporter) super
				.configureExporter(exp);
		exporter.getProperties().setProperty("ejb3",
				getComponentProperty("ejb3", "true"));
		exporter.getProperties().setProperty("jdk5",
				getComponentProperty("jdk5", "true"));

		if (generateCoreOnly) {
			exporter.getProperties().setProperty("generate-core", "true");
		} else if (generateWebOnly) {
			exporter.getProperties().setProperty("generate-web", "true");
		}

		String rootPackage = (fullPath != null) ? fullPath : getProject()
				.getGroupId();

		// AppFuse-specific values
		
		exporter.getProperties().setProperty("fType", this.functionType);
		//TODO 主子关系时需要额外添加的属性
		if(this.functionType.equals("2")){
			//子类全路径名
			exporter.getProperties().setProperty("detailFullPathName", this.detailPojoName);
			POJOClass detailPOJOClass = getPojoClassByFullPojoName(this.detailPojoName,exporter);
			
			//子类shortName
			String detailShortName= detailPOJOClass.getShortName();
			exporter.getProperties().setProperty("detailShortName",detailShortName);
			
			//子类shortNameLower
			String detailShortNameLower = detailShortName.substring(0,1).toLowerCase()+detailShortName.substring(1);
			exporter.getProperties().setProperty("detailShortNameLower",detailShortNameLower);
			
			POJOClass masterPOJOClass = getPojoClassByFullPojoName(this.masterPojoName,exporter);
			
			//子类shortName
			String masterShortName= masterPOJOClass.getShortName();
			exporter.getProperties().setProperty("masterShortName",masterShortName);
			//主类shortNameLower
			String masterShortNameLower = masterPOJOClass.getShortName().substring(0,1).toLowerCase()+masterPOJOClass.getShortName().substring(1);
			exporter.getProperties().setProperty("masterShortNameLower",masterShortNameLower);
			//主类id类型
			String masterIdType = masterPOJOClass.getJavaTypeName(masterPOJOClass.getIdentifierProperty(), true);
			exporter.getProperties().setProperty("masterIdType",masterIdType);
			//主类id名
			String masterIdName = masterPOJOClass.getIdentifierProperty().getName();
			exporter.getProperties().setProperty("masterIdName",masterIdName);
		}
		
		
		
		exporter.getProperties().setProperty("basepackage", rootPackage);
		exporter.getProperties().setProperty("daoframework",
				getProject().getProperties().getProperty("dao.framework"));

		String webFramework = (getProject().getProperties()
				.containsKey("web.framework")) ? getProject().getProperties()
				.getProperty("web.framework") : "";

		exporter.getProperties().setProperty("webframework", webFramework);

		exporter.getProperties().setProperty("packaging",
				getProject().getPackaging());
		exporter.getProperties().setProperty("genericcore",
				String.valueOf(genericCore));

		if (templateDirectory != null) {
			exporter.getProperties().setProperty("templatedirectory",
					templateDirectory);
		}

		if (isFullSource())
			exporter.getProperties().setProperty("appfusepackage", rootPackage);
		else {
			exporter.getProperties().setProperty("appfusepackage",
					"org.appfuse");
		}

		// See if the project has security enabled
		boolean hasSecurity = false;
		if (getProject().getPackaging().equals("war")) {
			Collection<File> sourceFiles = FileUtils.listFiles(getProject()
					.getBasedir(), new String[] { "xml" }, true);
			for (File file : sourceFiles) {
				if (file.getPath().contains("security.xml")) {
					hasSecurity = true;
					break;
				}
			}
		}

		exporter.getProperties().setProperty("hasSecurity",
				String.valueOf(hasSecurity));

		// determine if using Home or MainMenu for Tapestry
		if (webFramework.equals("tapestry")) {
			boolean useMainMenu = true;
			Collection<File> sourceFiles = FileUtils.listFiles(getProject()
					.getBasedir(), new String[] { "java" }, true);
			for (File file : sourceFiles) {
				if (file.getPath().contains("Home.java")) {
					useMainMenu = false;
					break;
				}
			}
			exporter.getProperties().setProperty("useMainMenu",
					String.valueOf(useMainMenu));
		}

		return exporter;
	}

	private POJOClass getPojoClassByFullPojoName(String fpn,GenericExporter ge){
		EntityPOJOClass e=null;
		Iterator iterator = ge.getCfg2JavaTool().getPOJOIterator(ge.getConfiguration().getClassMappings());
		Map additionalContext = new HashMap();
		while ( iterator.hasNext() ) {					
			 e = (EntityPOJOClass) iterator.next();
			 String n = e.getClass().getName();
			if((e.getPackageName()+"."+e.getShortName()).equals(fpn))
				return e;
			else e=null;
		}
		return e;
	}
	/**
	 * Executes the plugin in an isolated classloader.
	 * 
	 * @throws MojoExecutionException
	 *             When there is an erro executing the plugin
	 */
	@Override
	protected void doExecute() throws MojoExecutionException {
		super.doExecute();

		if (System.getProperty("disableInstallation") != null) {
			disableInstallation = Boolean.valueOf(System
					.getProperty("disableInstallation"));
		}

		// allow installation to be suppressed when testing
		if (!disableInstallation) {
			ArtifactInstaller installer = new ArtifactInstaller(getProject(),
					pojoName, sourceDirectory, destinationDirectory,
					this.fullPath, genericCore);
			installer.execute();
		}
	}

	/**
	 * Instantiates a org.appfuse.tool.AppFuseExporter object.
	 * 
	 * @return POJOExporter
	 */
	protected Exporter createExporter() {
		return new AppFuseExporter();
	}

	protected void setGenerateCoreOnly(boolean generateCoreOnly) {
		this.generateCoreOnly = generateCoreOnly;
	}

	protected void setGenerateWebOnly(boolean generateWebOnly) {
		this.generateWebOnly = generateWebOnly;
	}

	private void log(String msg) {
		getLog().info("[AppFuse] " + msg);
	}

	private void addEntityToHibernateCfgXml()
			throws MojoFailureException {
		
		String daoFramework = getProject().getProperties().getProperty(
				"dao.framework");

		// If dao.framework is jpa, change to jpaconfiguration and
		// persistence.xml should be found in classpath.
		// No other configuration is needed.
		/*if (daoFramework.indexOf("jpa") > -1) {
			getComponentProperties().put("implementation", "jpaconfiguration");
			checkEntityExists();
		}*/

		// for war projects that have a parent pom, don't reset classpath
		// this is to allow using hibernate.cfg.xml from core module
		/*if (getProject().getPackaging().equals("war")
				&& getProject().hasParent()) {
			// assume first module in parent project has hibernate.cfg.xml
			String moduleName = (String) getProject().getParent().getModules()
					.get(0);
			String pathToParent = getProject().getOriginalModel().getParent()
					.getRelativePath();
			pathToParent = pathToParent.substring(0,
					pathToParent.lastIndexOf('/') + 1);
			log("Assuming '"
					+ moduleName
					+ "' has hibernate.cfg.xml in its src/main/resources directory");
			getComponentProperties().put(
					"configurationfile",
					getProject().getBasedir() + "/" + pathToParent + moduleName
							+ "/src/main/resources/hibernate.cfg.xml");
		}*/

		if (getComponentProperty("configurationfile") == null) {
			getComponentProperties().put("configurationfile",
					"src/main/resources/hibernate.cfg.xml");
		}

		// if entity is not in hibernate.cfg.xml, add it
		String hibernateConfig = getComponentProperty("configurationfile");
		String hibernateCfgXml = null;//FileUtils.readFileToString(new File(				hibernateConfig));
		
		if (daoFramework.equals("hibernate")) {
			try {
				 hibernateCfgXml = FileUtils.readFileToString(new File(
						hibernateConfig));
				//addEntityToHibernateCfgXml(hibernateCfgXml);
			} catch (IOException io) {
				throw new MojoFailureException(io.getMessage());
			}
		}
		
		
		
		
		String className = (fullPath != null) ? fullPath : getProject()
				.getGroupId();

		className += ".model." + pojoName;
		log("Constructed class name : " + className);

		POJOSearcher pojoSearcher = new POJOSearcher(hibernateCfgXml);
		if (!pojoSearcher.searchForPojo(pojoName)) {
			// check that class exists and has an @Entity annotation
			checkEntityExists();

			hibernateCfgXml = hibernateCfgXml.replace("</session-factory>",
					"    <mapping class=\"" + className + "\"/>"
							+ "\n    </session-factory>");
			log("Adding '" + pojoName + "' to hibernate.cfg.xml...");
		}

		hibernateCfgXml = hibernateCfgXml.replaceAll("\\$\\{appfusepackage}",
				(isFullSource()) ? getProject().getGroupId() : "org.appfuse");

		try {
			FileUtils.writeStringToFile(
					new File(getComponentProperty("configurationfile",
							"src/main/resources/hibernate.cfg.xml")),
					hibernateCfgXml);
		} catch (IOException io) {
			throw new MojoFailureException(io.getMessage());
		}
	}

	private void checkEntityExists() throws MojoFailureException {
		// allow check to be bypassed when -Dentity.check=false
		if (!"false".equals(System.getProperty("entity.check"))) {
			final String FILE_SEP = System.getProperty("file.separator");
			// remove isTest from final and add null check.20121123 by Mr.fdc
			String isTest = System.getProperty("isTest");
			if (isTest == null)
				isTest = "false";

			String modelTypePath = "main";
			if (isTest.equalsIgnoreCase("true"))
				modelTypePath = "test";
			String pathToModelPackage = "src" + FILE_SEP + modelTypePath
					+ FILE_SEP + "java" + FILE_SEP;
			if (getProject().getPackaging().equals("war")
					&& getProject().hasParent()) {
				String moduleName = (String) getProject().getParent()
						.getModules().get(0);
				String pathToParent = getProject().getOriginalModel()
						.getParent().getRelativePath();
				pathToParent = pathToParent.substring(0,
						pathToParent.lastIndexOf('/') + 1);
				pathToParent = pathToParent.replaceAll("/", FILE_SEP);
				pathToModelPackage = getProject().getBasedir() + FILE_SEP
						+ pathToParent + moduleName + "/" + pathToModelPackage;
			}

			// refactor to check classpath instead of filesystem
			String groupIdAsPath = getProject().getGroupId().replace(".",
					FILE_SEP);

			// String fullPathPackage = pathToModelPackage + groupIdAsPath;
			String fullPathPackage = pathToModelPackage;// remove groupIdAsPath
														// var because entity
														// input full path name
			if (fullPath != null) {
				fullPathPackage += FILE_SEP + fullPath.replace(".", FILE_SEP)
						+ FILE_SEP + "model";// add model path
			} else {
				fullPathPackage += FILE_SEP + "model";
			}
			log("Looking for entity in " + fullPathPackage);

			File modelPackage = new File(this.destinationDirectory + "\\"
					+ fullPathPackage);// add this.destinationDirectory to the
										// file path
			boolean entityExists = false;

			if (modelPackage.exists()) {
				String[] entities = modelPackage.list();
				for (String entity : entities) {
					log("Found '" + entity + "' in model package...");
					if (entity.contains(pojoName + ".java")) {
						entityExists = true;
						break;
					}
				}
			} else {
				getLog().error(
						"The path '" + fullPathPackage + groupIdAsPath
								+ FILE_SEP + "model' does not exist!");
			}

			if (!entityExists) {
				throw new MojoFailureException("[ERROR] The '" + pojoName
						+ "' entity does not exist in '" + modelPackage + "'.");
			} else {
				// Entity found, make sure it has @Entity annotation
				try {
					File pojoFile = new File(modelPackage + FILE_SEP + pojoName
							+ ".java");
					String entityAsString = FileUtils
							.readFileToString(pojoFile);
					if (!entityAsString.contains("@Entity")) {
						String msg = "Entity '"
								+ pojoName
								+ "' found, but it doesn't contain an @Entity annotation. Please add one.";
						throw new MojoFailureException(msg);
					}
				} catch (IOException io) {
					throw new MojoFailureException("[ERROR] Class '" + pojoName
							+ ".java' not found in '" + modelPackage + "'");
				}
			}
		}
	}

	public class POJOSearcher extends DefaultHandler {
		private String pojoName;
		private boolean foundPojo;
		private String xmlString;

		public POJOSearcher(String xmlString) {
			this.xmlString = xmlString;
		}

		public boolean searchForPojo(String pojoName) {
			this.pojoName = pojoName;
			this.foundPojo = false;

			SAXParserFactory spf = SAXParserFactory.newInstance();
			try {
				SAXParser sp = spf.newSAXParser();
				// sp.setProperty("http://apache.org/xml/features/nonvalidating/load-external-dtd",
				// false);

				sp.parse(new InputSource(new StringReader(xmlString)), this);
			} catch (Exception ex) {
				System.out.println(ex.getMessage());
				ex.printStackTrace();
			}

			return foundPojo;
		}

		@Override
		public void startElement(String uri, String localName, String name,
				Attributes attributes) throws SAXException {
			super.startElement(uri, localName, name, attributes);
			if (name.equals("mapping")) {
				String classValue = attributes.getValue("class");
				if (classValue != null) {
					if (classValue.endsWith(pojoName)) {
						foundPojo = true;
					}
				}
			}
		}
	}
}
